#include <unistd.h>
#include <fcntl.h>
#include <vector>
#include "Util/logc.h"
#include "HidDevice.h"
#include "awi_dfu.h"




HidDevice::HidDevice(const std::string& device_path, std::string& product_model, const std::string& product_name, const std::string& phy_version, 
        const std::string& soft_version, const std::string& serial_number, 
        const std::string& unique_id, std::vector<HidDevInfo> sub_devices){
    m_device_file_path = device_path;

    m_product_model = product_model;
    m_product_name = product_name;
    m_phy_version = phy_version;
    m_soft_version = soft_version;
    m_serial_number = serial_number;
    m_unique_id = unique_id;
    m_sub_devices = sub_devices;
    m_isUpgrading = false;
}




bool HidDevice::Reset(){
    awi_dfu_t *dfu = awi_dfu_new();

    int fd = open(m_device_file_path.c_str(), O_RDWR, 0666);
    if(fd <= 0){
        errorf("open device fail %s\n", m_device_file_path.c_str());
        return false;
    }

    awi_dfu_set_send_notify(dfu, (void *)fd, [](void *notify_ths, const uint8_t *data, int bytes){
            int fd = (int)notify_ths;
            int8_t buffer[1024];
            int me_size = 256;
            buffer[0] = 5;
            memset(buffer + 1, 0, me_size - 1);
            memcpy(buffer + 1, data, bytes);
            write(fd, buffer, me_size);
            // printf("!!!!!!!!! send hid message fd %d, data bytes %d, write ret %d\n", fd, bytes, ret);
    });

    awi_dfu_set_recv_notify(dfu, nullptr, [](void *notify_ths, uint16_t ctrl, 
            uint16_t dst, uint16_t src, uint16_t version, 
            const uint8_t *payload, int bytes){
                // printf("!!!!!!!!! recv hid message\n");
                // deviceInfoRecv(notify_ths, ctrl, dst, src, version, payload, bytes);
    });

    awi_dfu_send(dfu, 0x0001, 0xff, 0x01, NULL, 0); //reset
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    close(fd);
    awi_dfu_del(dfu);
    return true;
}


bool HidDevice::Reboot(){
    awi_dfu_t *dfu = awi_dfu_new();

    int fd = open(m_device_file_path.c_str(), O_RDWR, 0666);
    if(fd <= 0){
        errorf("open device fail %s\n", m_device_file_path.c_str());
        return false;
    }

    awi_dfu_set_send_notify(dfu, (void *)fd, [](void *notify_ths, const uint8_t *data, int bytes){
            int fd = (int)notify_ths;
            int8_t buffer[1024];
            int me_size = 256;
            buffer[0] = 5;
            memset(buffer + 1, 0, me_size - 1);
            memcpy(buffer + 1, data, bytes);
            write(fd, buffer, me_size);
            // printf("!!!!!!!!! send hid message fd %d, data bytes %d, write ret %d\n", fd, bytes, ret);
    });

    awi_dfu_set_recv_notify(dfu, nullptr, [](void *notify_ths, uint16_t ctrl, 
            uint16_t dst, uint16_t src, uint16_t version, 
            const uint8_t *payload, int bytes){
                // printf("!!!!!!!!! recv hid message\n");
                // deviceInfoRecv(notify_ths, ctrl, dst, src, version, payload, bytes);
    });

    awi_dfu_send(dfu, 0x3f, 0xff, 0x01, NULL, 0); //reboot
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    close(fd);
    awi_dfu_del(dfu);
    return true;
}

bool HidDevice::GetDeviceBasicInfo(std::string& product_name, std::string& product_model, std::string& phy_version, 
        std::string& soft_version, std::string& serial_number, 
        std::string& unique_id, std::vector<HidDevInfo>& sub_devices){
    std::lock_guard<std::mutex> lock(m_field_lock);
    product_name = m_product_name;
    phy_version = m_phy_version;
    soft_version = m_soft_version;
    serial_number = m_serial_number;
    unique_id = m_unique_id;
    product_model = m_product_model;
    sub_devices   = m_sub_devices; 
  
    return true;
   
}

bool HidDevice::UpdateDevice(const std::string& device_path, const std::string& product_name, const std::string& phy_version, 
    const std::string& soft_version, const std::string& serial_number, 
    const std::string& unique_id, std::vector<HidDevInfo> sub_devices){
    std::lock_guard<std::mutex> lock(m_field_lock);
    bool mark = false;
    if(m_device_file_path != device_path){
        m_device_file_path = device_path;
        mark = true;
    }
    
    if(m_product_name != product_name){
        m_product_name = product_name;
        mark = true;
    }

    if(m_phy_version != phy_version){
        m_phy_version = phy_version;
        mark = true;
    }
    
    if(m_soft_version != soft_version){
        m_soft_version = soft_version;
        mark = true;
    }

    if(m_serial_number != serial_number){
        m_product_name = product_name;
        mark = true;
    }


    if(m_unique_id != unique_id){
        m_unique_id = unique_id;
        mark = true;
    }

    m_sub_devices = sub_devices;
    return true;
}

bool HidDevice::IsUpgrading(){
    return m_upgrade->IsUpgrading();
}

int HidDevice::GetUpgradeProgress(){
    
    return m_upgrade->GetUpgradeProgress();
}

static void deviceInfoRecv(void *obj,uint16_t ctrl, uint16_t dst, uint16_t src, uint16_t version, 
            const uint8_t *payload, int bytes){
    
    HidInfo *info = (HidInfo *)obj;
    // aw_print("ctrl = %x dst = %x src = %x version = %d bytes = %d", ctrl, dst, src, version, bytes);
	// printf("recved payload [");
	// for (int i = 0; i < bytes; ++i) {
	// 	printf("%x ", payload[i]);
	// }
	// printf("]\r\n");

	switch (ctrl) {
        case 0x0081:
        {
            uint32_t *pv = (uint32_t*)payload;
            uint32_t rlt = awi_ntohl(pv[0]);
            if (rlt == 0) {
                // aw_print("reset success");
            }
            else if (rlt == 1) {
                aw_print("reset fail and dev will reboot");
            }
            else {
                aw_print("unexpect rlt = %d", rlt);
            }
            break;
        }
        case 0x0082:
        {
            uint16_t *pv = (uint16_t*)payload;
            uint16_t dev_count = awi_htons(pv[0]);
            
            int pos = 2;
            for (int i = 0; i < dev_count; ++i) {
                HidDevInfo dev;
                uint16_t dev_type;
                char product_name[64];
                char phy_version[64];
                char soft_version[64];
                char serial_number[64];
                char unique_id[64];

                product_name[0] = '\0';
                phy_version[0] = '\0';
                soft_version[0] = '\0';
                serial_number[0] = '\0';
                unique_id[0] = '\0';
                
                uint16_t *ptype = (uint16_t*)(payload + pos);
                pos += 2;
                dev_type = awi_ntohs(ptype[0]);
                uint32_t *pflag = (uint32_t*)(payload + pos);
                uint32_t flag = awi_ntohl(pflag[0]);
                pos += 4;
                
    #define PRODUCT_NAME_SHIFT 0
    #define PHY_VERSION_SHIFT 1
    #define SOFT_VERSION_SHIFT 2
    #define SERIAL_NUMBER_SHIFT 3
    #define UNIQUE_ID_SHIFT 4

                for (int j = 0; j < 32; ++j) {
                    if ((flag & (1 << j)) == 0) {
                        continue;
                    }
                    // aw_print("catch shift %d", j);
                    switch (j) {
                    case PRODUCT_NAME_SHIFT: 
                    {
                        uint8_t len = payload[pos++];
                        memcpy(product_name, payload + pos, len);
                        product_name[len] = '\0';
                        pos += len;
                        break;
                    }
                    case PHY_VERSION_SHIFT:
                    {
                        uint8_t len = payload[pos++];
                        memcpy(phy_version, payload + pos, len);
                        phy_version[len] = '\0';
                        pos += len;
                        break;
                    }
                    case SOFT_VERSION_SHIFT:
                    {
                        uint8_t len = payload[pos++];
                        memcpy(soft_version, payload + pos, len);
                        soft_version[len] = '\0';
                        pos += len;
                        break;
                    }
                    case SERIAL_NUMBER_SHIFT:
                    {
                        uint8_t len = payload[pos++];
                        memcpy(serial_number, payload + pos, len);
                        serial_number[len] = '\0';
                        pos += len;
                        break;
                    }
                    case UNIQUE_ID_SHIFT:
                    {
                        uint8_t len = payload[pos++];
                        memcpy(unique_id, payload + pos, len);
                        unique_id[len] = '\0';
                        pos += len;
                        break;
                    }
                    default: {
                        uint8_t len = payload[pos++];
                        pos += len;
                        break;
                    }
                    }
                }
                //aw_print("%d : dev type = [%x] product name = [%s] phy version = [%s] soft version = [%s] serial number = [%s] unique id = [%s]",
                //            i, dev_type, product_name, phy_version, soft_version, serial_number, unique_id);
                if(i == 0){
                    dev.mainDevice = true;
                    info->m_success_getinfo = true;
                }else{
                    dev.mainDevice = false;
                }
                dev.m_product_name = product_name;
                dev.m_phy_version = phy_version;
                dev.m_soft_version = soft_version;
                dev.m_serial_number = serial_number;
                dev.m_unique_id = unique_id;
                info->subDevices.push_back(dev);
            }
            break;
        }
	}
}

bool GetDeviceBasicInfo(std::string device_path, std::vector<HidDevInfo>& subDevices){
    HidInfo info;
    // printf("############### GetDeviceBasicInfo start###############\n");
    awi_dfu_t *dfu = awi_dfu_new();

    int fd = open(device_path.c_str(), O_RDWR, 0666);
    if(fd <= 0){
        printf("open device fail %s\n", device_path.c_str());
        return false;
    }

    awi_dfu_set_send_notify(dfu, (void *)fd, [](void *notify_ths, const uint8_t *data, int bytes){
            int fd = (int)notify_ths;

            int8_t buffer[1024];
            int me_size = 256;
            buffer[0] = 5;
            memset(buffer + 1, 0, me_size - 1);
            memcpy(buffer + 1, data, bytes);
            write(fd, buffer, me_size);
            // printf("!!!!!!!!! send hid message fd %d, data bytes %d, write ret %d\n", fd, bytes, ret);
    });

    awi_dfu_set_recv_notify(dfu, &info, [](void *notify_ths, uint16_t ctrl, 
            uint16_t dst, uint16_t src, uint16_t version, 
            const uint8_t *payload, int bytes){
                // printf("!!!!!!!!! recv hid message\n");
                deviceInfoRecv(notify_ths, ctrl, dst, src, version, payload, bytes);
    });


    auto read_process = [&info](int fd, awi_dfu_t * dfu){
        fd_set rfds;
        FD_ZERO(&rfds);
		FD_SET(fd, &rfds);
        char buffer[1024];

        struct timeval tv;
        tv.tv_sec = 1; 
        tv.tv_usec = 0;

        int max_fail_count = 5;
        while (!info.m_success_getinfo && max_fail_count > 0) {
            int ret = select(fd + 1, &rfds, NULL, NULL, &tv);
            if(ret <= 0){
               max_fail_count--;
               continue;
            }
            if (FD_ISSET(fd, &rfds)){
                int len = read(fd, buffer, 1024);
                // aw_print("len = %d", len);
                if (len <= 0) {
                    printf("read   failed \n");
                    break;
                }
                // printf("recv[");
                // for (int i = 0; i < len; ++i) {
                //     printf("%x ", (uint8_t)buffer[i]);
                // }
                // printf("]\r\n");
                awi_dfu_recv(dfu, buffer+1, len+1);
            }
        }
    };

    auto deviceinfo_task = std::async(std::launch::async, read_process, fd, dfu);

    awi_dfu_send(dfu, 0x01, 0xFFFF, 0x01, NULL, 0);
    awi_dfu_send(dfu, 0x02, 0xFFFF, 0x01, NULL, 0);
    
    deviceinfo_task.wait();

    awi_dfu_del(dfu);
    close(fd);
    // printf("############### GetDeviceBasicInfo end###############\n");
    if(!info.m_success_getinfo) return false;

    subDevices = info.subDevices;
   
    
    return true;
}
